package org.lds.hotkey.client.holder;

import io.etcd.jetcd.KeyValue;
import io.etcd.jetcd.Watch;
import io.etcd.jetcd.watch.WatchEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.lds.hotkey.client.autoconfigure.component.AppContext;
import org.lds.hotkey.client.common.ClientErrorEnum;
import org.lds.hotkey.common.autoconfigure.etcd.EtcdService;
import org.lds.hotkey.common.autoconfigure.spring.SpringUtil;
import org.lds.hotkey.common.constant.KeyConstant;
import org.lds.hotkey.common.enums.ErrorEnum;
import org.lds.hotkey.common.model.Entity;
import org.lds.hotkey.common.model.HotKeyModel;
import org.lds.hotkey.common.util.AssertUtil;
import org.lds.hotkey.common.util.EntityModelUtil;
import org.lds.hotkey.common.util.JsonUtil;
import org.lds.hotkey.common.util.MapUtil;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class HotKeyHolder {
    private static final EtcdService etcdService = SpringUtil.getBean(EtcdService.class);

    /**
     * key: 当前appId 或者 0
     * value:
     *     key: key
     *     value: HotKeyModel
     */
    private static final Map<Long, Map<String, HotKeyModel>> HOT_KEY_MAP = new ConcurrentHashMap<>();

    public static void start() {
        AssertUtil.assertTrue(Objects.nonNull(AppContext.appId), ClientErrorEnum.APP_ID_NOT_EXIST);

        init();
        watch();
    }

    /**
     * 初始化 HOT_KEY_MAP
     */
    private static void init() {
        init(KeyConstant.HOT_KEY_DIRECTORY + "0");
        init(KeyConstant.HOT_KEY_DIRECTORY + AppContext.appId);
        log.info("HOT_KEY_MAP={}", JsonUtil.toJson(HOT_KEY_MAP));
    }

    private static void init(String keyPath) {
        List<KeyValue> keyValues = etcdService.listByPrefix(keyPath);
        AssertUtil.assertTrue(Objects.nonNull(keyValues), ErrorEnum.ETCD_UNAVAILABLE);

        for (KeyValue keyValue : keyValues) {
            Entity<HotKeyModel> entity = EntityModelUtil.getEntity(keyValue, HotKeyModel.class);
            if (Objects.nonNull(entity)) {
                MapUtil.fillMapMap(HOT_KEY_MAP, entity.getAppId(), entity.getValue().getKey(), entity.getValue());
            }
        }
    }

    private static void watch() {
        watch(KeyConstant.HOT_KEY_DIRECTORY + "0");
        watch(KeyConstant.HOT_KEY_DIRECTORY + AppContext.appId);
    }

    private static void watch(String keyPath) {
        Watch.Watcher watcher = etcdService.watchKey(keyPath, watchResponse -> {
            for (WatchEvent event : watchResponse.getEvents()) {
                Entity<HotKeyModel> entity = EntityModelUtil.getEntity(event, HotKeyModel.class);
                if (Objects.isNull(entity)) continue;

                if (event.getEventType() == WatchEvent.EventType.PUT) {
                    MapUtil.fillMapMap(HOT_KEY_MAP, entity.getAppId(), entity.getValue().getKey(), entity.getValue());
                } else {
                    MapUtil.removeMapMap(HOT_KEY_MAP, entity.getAppId(), entity.getValue().getKey());
                }
            }
            log.info("HOT_KEY_MAP={}", JsonUtil.toJson(HOT_KEY_MAP));
        });
        AssertUtil.assertTrue(Objects.nonNull(watcher), ErrorEnum.ETCD_UNAVAILABLE);
    }

    public static HotKeyModel getHotKeyModel(String key) {
        HotKeyModel hotKeyModel = getHotKeyModel(AppContext.appId, key);
        if (Objects.nonNull(hotKeyModel)) return hotKeyModel;
        return getHotKeyModel(0L, key);
    }

    private static HotKeyModel getHotKeyModel(Long appId, String key) {
        if (Objects.isNull(appId) || StringUtils.isEmpty(key)) return null;

        Map<String, HotKeyModel> map = HOT_KEY_MAP.get(appId);
        if (MapUtils.isEmpty(map)) return null;

        return map.get(key);
    }

}
